package com.qianfeng.day11.util;

public class Snowflake {


    /** 工作机器ID(0~31) */
    private Long workerId;


    /** 数据中心ID(0~31) */
    private Long datancenterId;


    /** 毫秒内序列(0~4095) */
    private Long sequence;


    /**开始时间戮(2019-11-15 17:58:44)       https://tool.lu/timestamp/     */
    private long twepoch = 1573811924000L;


    /**机器id所占的位数*/
    private Long workerIdBits = 5L;


    /**数据标识id所占的位数*/
    private Long datacenterIdBits = 5L;


    /** 支持的最大机器id，结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private Long maxWorkId = -1L ^ (-1L << workerIdBits);


    /** 支持的最大数据标识id，结果是31 */
    private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);


    /** 序列在id中占的位数 */
    private Long sequenceBits = 12L;


    /** 机器ID向左移12位 */
    private Long workerIdShift = sequenceBits;


    /** 数据标识(机房)id向左移17位(12+5) */
    private Long datacenterIdShift = sequenceBits + workerIdBits;


    /** 时间戮向左移22位(5+5+12) */
    private Long timestampleftShift = sequenceBits + workerIdBits + datacenterIdBits;


    /** 生成序列的掩码，这里为4095 (0b111111111111=0xfff=4095) */
    private Long sequenceMask = -1 ^ (-1L << sequenceBits);


    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;


    public Snowflake(Long workerId, Long datancenterId, Long sequence){

        //检查传递进来的机房id和机器id不能超过最大值32，不能小于0
        if(workerId > maxWorkId || workerId < 0){
            throw  new IllegalArgumentException(
                    String.format("worker id can't be greater than %d or less than 0",maxWorkId));
        }

        if (datancenterId > maxDatacenterId || datancenterId < 0){
            throw  new IllegalArgumentException(
                    String.format("datancenter id can't be greater than %d or less than 0",maxDatacenterId));
        }


        this.workerId = workerId;
        this.datancenterId = datancenterId;
        this.sequence = sequence;
    }



    public Long getWorkerId(){
        return workerId;
    }

    public Long getDatancenterId(){
        return datancenterId;
    }

    public Long getTimestamp(){
        return System.currentTimeMillis();
    }

    public synchronized Long nextId(){

        //获取当前时间   单位毫秒
        Long timestamp = timeGen();

        //如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
        if(timestamp < lastTimestamp){
            throw new RuntimeException(
                    String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
                            lastTimestamp - timestamp));

        }

        //如果是同一时间生成，这个时候把sequence序号增加1，最多4096
        if (lastTimestamp == timestamp) {

            //一毫秒内最多只能有4096个数字，无论你传递多少进来，这个位运算都会保证在4096的范围内
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }else {
            //时间戮改变，毫秒内序列重置。
            sequence = 0L;
        }

        //记录最近一次生成id的时间戮
        lastTimestamp = timestamp;

        //这儿就是最核心的二进制位运算操作，生成一个64bit的id
        // 先将当前时间戳左移，放到41 bit那儿；将机房id左移放到5 bit那儿；将机器id左移放到5 bit那儿；将序号放最后12 bit
        //最后拼接起来成一个64 bit的二进制数字，转换成10进制就是个long型
        return ((timestamp - twepoch) << timestampleftShift)
                |   (datancenterId << datacenterIdShift)
                |   (workerId << workerIdShift)
                |   sequence;
    }

    /**
     * 阻塞到下一个毫秒，直到获取新的时间戮
     * @param lastTimestamp 上次生成ID的时间戮
     * @return 当前的时间戮
     */
    public Long tilNextMillis(Long lastTimestamp){
        Long timestamp = timeGen();

        while(timestamp <= lastTimestamp){
            timestamp = timeGen();
        }
        return timestamp;
    }


    /**
     * 返回当前时间
     * @return 当前时间  毫秒
     */
    public Long timeGen(){
        return System.currentTimeMillis();
    }


    private Snowflake(){}

    private static Snowflake instance = new Snowflake(1L,1L,1L);;

    public static Snowflake getInstance(){
        return instance;
    }

    public static void main(String[] args) {
        Snowflake worker = new Snowflake(1L,1L,1L);
        for (int  i=0;i<30;i++){
            System.out.println(worker.nextId());
        }
    }

}
